' Stephen Toub


Imports Microsoft.VisualBasic
Imports System
Imports System.IO
Imports System.Drawing
Imports System.Windows.Forms
Imports System.Runtime.InteropServices
Imports System.ComponentModel
Imports Microsoft.Win32
Imports System.Runtime.InteropServices.ComTypes
Imports System.Threading
Imports System.Diagnostics
Imports System.Collections.Generic

Namespace C4F.DevKit.PreviewHandler.PreviewHandlerFramework
    Public MustInherit Class PreviewHandler
        Implements IPreviewHandler, IPreviewHandlerVisuals, IOleWindow, IObjectWithSite
        Private _showPreview As Boolean
        Private _previewControl As PreviewHandlerControl
        Private _parentHwnd As IntPtr
        Private _windowBounds As Rectangle
        Private _unkSite As Object
        Private _frame As IPreviewHandlerFrame
        Private _result As IntPtr
        Private _color As Color
        Private _font As Font

        Protected Sub New()
            _previewControl = CreatePreviewHandlerControl() ' NOTE: shouldn't call virtual function from constructor; see article for more information
            Dim forceCreation As IntPtr = _previewControl.Handle
            _previewControl.BackColor = SystemColors.Window
        End Sub

        Protected MustOverride Function CreatePreviewHandlerControl() As PreviewHandlerControl

        Private Sub InvokeOnPreviewThread(ByVal d As MethodInvoker)
            _previewControl.Invoke(d)
        End Sub

        <DllImport("user32.dll")> _
        Private Shared Function SetParent(ByVal hWndChild As IntPtr, ByVal hWndNewParent As IntPtr) As IntPtr
        End Function

        Private Sub UpdateWindowBounds()
            If _showPreview Then
                InvokeOnPreviewThread(AddressOf AnonymousMethod1)
            End If
        End Sub
        Private Sub AnonymousMethod1()
            SetParent(_previewControl.Handle, _parentHwnd)
            _previewControl.Bounds = _windowBounds
            _previewControl.Visible = True
        End Sub

        Private Sub SetWindow(ByVal hwnd As IntPtr, ByRef rect As RECT) Implements IPreviewHandler.SetWindow
            _parentHwnd = hwnd
            _windowBounds = rect.ToRectangle()
            UpdateWindowBounds()
        End Sub

        Private Sub SetRect(ByRef rect As RECT) Implements IPreviewHandler.SetRect
            _windowBounds = rect.ToRectangle()
            UpdateWindowBounds()
        End Sub

        Protected MustOverride Sub Load(ByVal c As PreviewHandlerControl)

        Private Sub DoPreview() Implements IPreviewHandler.DoPreview
            _showPreview = True
            InvokeOnPreviewThread(AddressOf AnonymousMethod2)
        End Sub
        Private Sub AnonymousMethod2()
            Try
                Load(_previewControl)
            Catch exc As Exception
                _previewControl.Controls.Clear()
                Dim text As TextBox = New TextBox()
                text.ReadOnly = True
                text.Multiline = True
                text.Dock = DockStyle.Fill
                text.Text = exc.ToString()
                _previewControl.Controls.Add(text)
            End Try
            UpdateWindowBounds()
        End Sub

        Private Sub Unload() Implements IPreviewHandler.Unload
            _showPreview = False
            InvokeOnPreviewThread(AddressOf AnonymousMethod3)
        End Sub
        Private Sub AnonymousMethod3()
            _previewControl.Visible = False
            _previewControl.Unload()
        End Sub

        Private Sub SetFocus() Implements IPreviewHandler.SetFocus
            InvokeOnPreviewThread(AddressOf AnonymousMethod4)
        End Sub
        Private Sub AnonymousMethod4()
            _previewControl.Focus()
        End Sub

        <DllImport("user32.dll", CharSet:=CharSet.Auto)> _
        Private Shared Function GetFocus() As IntPtr
        End Function

        Private Sub QueryFocus(<System.Runtime.InteropServices.Out()> ByRef phwnd As IntPtr) Implements IPreviewHandler.QueryFocus
            _result = IntPtr.Zero
            InvokeOnPreviewThread(AddressOf AnonQueryFocus)

            phwnd = _result
            If phwnd = IntPtr.Zero Then
                Throw New Win32Exception()
            End If
        End Sub
        Private Sub AnonQueryFocus()
            _result = GetFocus()
        End Sub

        Public Delegate Function PredicateWrapperDelegate(Of T, A) _
        (ByVal item As T, ByVal argument As A) As Boolean

        Private Class PredicateWrapper(Of T, A)
            Private _argument As A
            Private _wrapperDelegate As PredicateWrapperDelegate(Of T, A)

            Public Sub New(ByVal argument As A, _
                ByVal wrapperDelegate As PredicateWrapperDelegate(Of T, A))

                _argument = argument
                _wrapperDelegate = wrapperDelegate
            End Sub

            Private Function InnerPredicate(ByVal item As T) As Boolean
                Return _wrapperDelegate(item, _argument)
            End Function

            Public Shared Widening Operator CType( _
                ByVal wrapper As PredicateWrapper(Of T, A)) _
                As Predicate(Of T)

                Return New Predicate(Of T)(AddressOf wrapper.InnerPredicate)
            End Operator

        End Class


        Private Function TranslateAccelerator(ByRef pmsg As MSG) As UInteger Implements IPreviewHandler.TranslateAccelerator
            If Not _frame Is Nothing Then
                Return _frame.TranslateAccelerator(pmsg)
            End If
            Const S_FALSE As UInteger = 1
            Return S_FALSE
        End Function

        Private Sub SetBackgroundColor(ByVal color As COLORREF) Implements IPreviewHandlerVisuals.SetBackgroundColor
            _color = color.Color
            InvokeOnPreviewThread(AddressOf AnonSetBackColor)
        End Sub
        Private Sub AnonSetBackColor()
            _previewControl.BackColor = _color

        End Sub

        Private Sub SetTextColor(ByVal color As COLORREF) Implements IPreviewHandlerVisuals.SetTextColor
            _color = color.Color
            InvokeOnPreviewThread(AddressOf AnonSetTextColor)
        End Sub
        Private Sub AnonSetTextColor()
            _previewControl.ForeColor = _color
        End Sub

        Private Sub SetFont(ByRef plf As LOGFONT) Implements IPreviewHandlerVisuals.SetFont
            _font = Font.FromLogFont(plf)
            InvokeOnPreviewThread(AddressOf AnonSetFont)
        End Sub
        Private Sub AnonSetFont()
            _previewControl.Font = _font
        End Sub

        Private Sub GetWindow(<System.Runtime.InteropServices.Out()> ByRef phwnd As IntPtr) Implements IOleWindow.GetWindow
            phwnd = IntPtr.Zero
            phwnd = _previewControl.Handle
        End Sub

        Private Sub ContextSensitiveHelp(ByVal fEnterMode As Boolean) Implements IOleWindow.ContextSensitiveHelp
            Throw New NotImplementedException()
        End Sub

        Private Sub SetSite(ByVal pUnkSite As Object) Implements IObjectWithSite.SetSite
            _unkSite = pUnkSite
            _frame = TryCast(_unkSite, IPreviewHandlerFrame)
        End Sub

        Private Sub GetSite(ByRef riid As Guid, <System.Runtime.InteropServices.Out()> ByRef ppvSite As Object) Implements IObjectWithSite.GetSite
            ppvSite = _unkSite
        End Sub

        <ComRegisterFunction()> _
        Public Shared Sub Register(ByVal t As Type)
            If Not t Is Nothing AndAlso t.IsSubclassOf(GetType(PreviewHandler)) Then
                Dim attrs As Object() = CType(t.GetCustomAttributes(GetType(PreviewHandlerAttribute), True), Object())
                If Not attrs Is Nothing AndAlso attrs.Length = 1 Then
                    Dim attr As PreviewHandlerAttribute = TryCast(attrs(0), PreviewHandlerAttribute)
                    RegisterPreviewHandler(attr.Name, attr.Extension, t.GUID.ToString("B"), attr.AppId)
                End If
            End If
        End Sub

        <ComUnregisterFunction()> _
        Public Shared Sub Unregister(ByVal t As Type)
            If Not t Is Nothing AndAlso t.IsSubclassOf(GetType(PreviewHandler)) Then
                Dim attrs As Object() = CType(t.GetCustomAttributes(GetType(PreviewHandlerAttribute), True), Object())
                If Not attrs Is Nothing AndAlso attrs.Length = 1 Then
                    Dim attr As PreviewHandlerAttribute = TryCast(attrs(0), PreviewHandlerAttribute)
                    UnregisterPreviewHandler(attr.Extension, t.GUID.ToString("B"), attr.AppId)
                End If
            End If
        End Sub

        Protected Shared Sub RegisterPreviewHandler(ByVal name As String, ByVal extensions As String, ByVal previewerGuid As String, ByVal appId As String)
            ' Create a new prevhost AppID so that this always runs in its own isolated process
            Using appIdsKey As RegistryKey = Registry.ClassesRoot.OpenSubKey("AppID", True)
                Using appIdKey As RegistryKey = appIdsKey.CreateSubKey(appId)
                    appIdKey.SetValue("DllSurrogate", "%SystemRoot%\system32\prevhost.exe", RegistryValueKind.ExpandString)
                End Using
            End Using

            ' Add preview handler to preview handler list
            Using handlersKey As RegistryKey = Registry.LocalMachine.OpenSubKey("SOFTWARE\Microsoft\Windows\CurrentVersion\PreviewHandlers", True)
                handlersKey.SetValue(previewerGuid, name, RegistryValueKind.String)
            End Using

            ' Modify preview handler registration
            Using clsidKey As RegistryKey = Registry.ClassesRoot.OpenSubKey("CLSID")
                Using idKey As RegistryKey = clsidKey.OpenSubKey(previewerGuid, True)
                    idKey.SetValue("DisplayName", name, RegistryValueKind.String)
                    idKey.SetValue("AppID", appId, RegistryValueKind.String)
                    idKey.SetValue("DisableLowILProcessIsolation", 1, RegistryValueKind.DWord) ' optional, depending on what preview handler needs to be able to do
                End Using
            End Using

            For Each extension As String In extensions.Split(New Char() {";"c}, StringSplitOptions.RemoveEmptyEntries)

                ' Set preview handler for specific extension
                Using extensionKey As RegistryKey = Registry.ClassesRoot.CreateSubKey(extension)
                    Using shellexKey As RegistryKey = extensionKey.CreateSubKey("shellex")
                        Using previewKey As RegistryKey = shellexKey.CreateSubKey("{8895b1c6-b41f-4c1c-a562-0d564250836f}")
                            previewKey.SetValue(Nothing, previewerGuid, RegistryValueKind.String)
                        End Using
                    End Using
                End Using
            Next extension
        End Sub

        Protected Shared Sub UnregisterPreviewHandler(ByVal extensions As String, ByVal previewerGuid As String, ByVal appId As String)
            For Each extension As String In extensions.Split(New Char() {";"c}, StringSplitOptions.RemoveEmptyEntries)
                Using shellexKey As RegistryKey = Registry.ClassesRoot.OpenSubKey(extension & "\shellex", True)
                    shellexKey.DeleteSubKey("{8895b1c6-b41f-4c1c-a562-0d564250836f}")
                End Using
            Next extension

            Using appIdsKey As RegistryKey = Registry.ClassesRoot.OpenSubKey("AppID", True)
                appIdsKey.DeleteSubKey(appId)
            End Using

            Using classesKey As RegistryKey = Registry.LocalMachine.OpenSubKey("SOFTWARE\Microsoft\Windows\CurrentVersion\PreviewHandlers", True)
                classesKey.DeleteValue(previewerGuid)
            End Using
        End Sub
    End Class
End Namespace